#pragma once 

#include <string>
#include <sstream>

#include "../ObservableEvent.h"
#include "../../VectorTimer/VectorTimer.h"
#include "../../VectorTimer/FixedVectorTimer.h"
#include "../../UtilityClasses/Common.h"



using std::stringstream;
namespace Network
{
	namespace ObservableEvents
	{
		template<typename T, typename S>
		class ObservableEventsList
		{
			public:
				virtual void MergeEvents(string p_inputString) = 0;
				virtual void MergeEvents(vector<unsigned char>& p_allData, int p_skip = 0) = 0;
				virtual vector<ObservableEvent<T,S>*> DataToMerge(vector<unsigned char>& p_allData) = 0;
				virtual vector<ObservableEvent<T, S>*> DataToMerge(vector<unsigned char>& p_allData, int p_amount) = 0;
				virtual void FindMissingOE(vector<unsigned char>& p_array, vector<unsigned char>& p_outputArray) = 0;


				///-------------------------------------------------------------------------------------------------
				/// <summary>	Constructor. </summary>
				///
				/// <remarks>	Adrian, 21/09/2015. </remarks>
				///
				/// <param name="p_peerIndex">	Zero-based index of the peer. </param>
				///-------------------------------------------------------------------------------------------------

				ObservableEventsList(int p_peerIndex, int p_amountPeers)
				{
					m_peerLogicalTime = new FixedVectorTimer(p_peerIndex,p_amountPeers);
					m_highEpochCounter = DELTAGAP_EPOCH;
					m_lowEpochCounter = 0;
					
				}

				///-------------------------------------------------------------------------------------------------
				/// <summary>	Constructor. </summary>
				///
				/// <remarks>	Adrian, 21/09/2015. </remarks>
				///
				/// <param name="p_recievedObservableEvents">	The recieved observable events. </param>
				///-------------------------------------------------------------------------------------------------

				/*ObservableEventsList(string p_recievedObservableEvents)
				{
					string splitString[2];
					HelperClass::ParseStringSentence(p_recievedObservableEvents, "&", splitString);

					vector<string> inputObservaleEvents;
					HelperClass::ParseStringSentence(splitString[1], ";", inputObservaleEvents);

					m_peerLogicalTime = new FixedVectorTimer(splitString[0]);
					for (const auto &iter : inputObservaleEvents)
					{
						m_observableEvents.push_back(new ObservableEvent<T>(*iter));
					}
				}
*/
				void AddObservableEvent(string p_toAdd)
				{
					ObservableEvent<T,S>* currentEvent = new ObservableEvent<T,S>(p_toAdd ,  m_highEpochCounter );
					m_highEpochCounter++;
					m_observableEvents[currentEvent->GetUuid()] = currentEvent;
				}

				void AddUpdate(string p_toAdd)
				{
					ObservableEvents::ObservableEvent<T,S>* currentUpdate = new ObservableEvent<T,S>(p_toAdd);
					m_observableEvents.push_back(currentUpdate);
				}

				///-------------------------------------------------------------------------------------------------
				/// <summary>	Merge events. inv + inv = inv ins + ins = ins T(ins, inv) inv + ins = ins ins + inv = inv </summary>
				///
				/// <remarks>	Adrian, 10/09/2015. </remarks>
				///
				/// <param name="p_observableEvents">	[in,out] If non-null, the observable events. </param>
				///-------------------------------------------------------------------------------------------------

				void GetListEvents(string p_stringEvents, vector<string>& p_outputVector)
				{
					vector<string> stringEvents;
					HelperClass::ParseStringSentence(p_stringEvents, ";", stringEvents);
					for (const auto &iter : stringEvents)
					{
						p_outputVector.push_back(new ObservableEvent<T,S>(*iter));
					}
				}
				

				void AddEventToList(ObservableEvent<T,S>* p_currentEvent)
				{
					p_currentEvent->SetEpoch(m_lowEpochCounter);
					m_lowEpochCounter++;
					m_peerLogicalTime->IncrementTimer();
					p_currentEvent->SetTimeStamp(m_peerLogicalTime); 

					m_observableEvents.push_back(p_currentEvent);
					IncrementTimer();

					if (m_observableEvents.size() >= MAX_AMOUNT_OBSERVABLE_ELEMENTS)
					{
						//compact list
						GroupEvents();
					}
				}

				void GroupEvents()
				{
					//talk to keith if its this way
				}

				///-------------------------------------------------------------------------------------------------
				/// <summary>	Convert this object into a string representation. </summary>
				///
				/// <remarks>	Adrian, 10/09/2015. </remarks>
				///
				/// <returns>	A string that represents this object. </returns>
				///-------------------------------------------------------------------------------------------------

				string ToString()
				{
					stringstream output;

					output << m_peerLogicalTime->ToString();
					output << "&";

					for (int index = 0; index < m_observableEvents.size(); index ++)
					{
						output << m_observableEvents[index]->ToString()<< "*";
					}

					return output.str();
				}

				string ToString(string p_requestedOe)
				{
					stringstream output;

					//append vector timer 

					output << m_peerLogicalTime->ToString();
					output << "&";
					output << GetRequestedOe(p_requestedOe);
					return output.str();
				}


				///-------------------------------------------------------------------------------------------------
				/// <summary>	Increment timer. </summary>
				///
				/// <remarks>	Adrian, 10/09/2015. </remarks>
				///-------------------------------------------------------------------------------------------------

				void IncrementTimer()
				{
					m_peerLogicalTime->IncrementTimer();
				}


				bool operator == (ObservableEventsList* p_otherList)
				{
					int index = 0;
					for (int index = 0; index < m_observableEvents.size(); index++)
					{
						if (m_observableEvents[index] != p_otherList->m_observableEvents[index])
							return false;
					}
					return true;
				}

				void SetVectorTimer(VectorTimer* p_vectorTimer)
				{
					m_peerLogicalTime = (FixedVectorTimer*)p_vectorTimer;
				}

				bool CheckNewUpdates()
				{
					return newUpdates.size() > 0;
				}

				void AddObservableEvent(ObservableEvent<T, S>* p_observableEvent)
				{
					m_observableEvents.push_back(p_observableEvent);
				}

				int GetAmountObservablEvents()
				{
					return m_observableEvents.size();
				}

				string GetRequestedOe(string p_uuidList)
				{
					vector<string> requestedUuid;
					HelperClass::ParseStringSentence(p_uuidList, ";", requestedUuid);
					
					stringstream ss;

					for (int index = 0; index < requestedUuid.size(); index++)
					{
						ss << GetObservableEvent(boost::lexical_cast<uuid>(requestedUuid[index]))->ToString() << "*";
					}

					return ss.str();
				}

				string GetMissingOes(string p_uuidList)
				{
					vector<string> allUuids;
					HelperClass::ParseStringSentence(p_uuidList, ";", allUuids);
					stringstream ss;
					for (int index = 0; index < allUuids.size(); index++)
					{
						if (!find(boost::lexical_cast<uuid>(allUuids[index])))
							ss << allUuids[index]<< ";";
					}

					return ss.str();
				}

				vector<uuid> GetMissingOes(vector<uuid> p_allUuids)
				{
					vector<uuid> neededUuids;
					for (int index = 0; index < p_allUuids.size(); index++)
					{
						if (!find(p_allUuids[index]))
							neededUuids.push_back(p_allUuids[index]);
					}

					return ss.str();
				}

				void SerializeUuid(vector<unsigned char>& p_array, uuid p_currentUuid)
				{
					unsigned char* uuidArray = (unsigned char*)&p_currentUuid;

					for (int index = 0; index < sizeof(boost::uuids::uuid); index++)
					{
						p_array.push_back(uuidArray[index]);
					}
				}

				virtual vector<uuid> ParseByteArrayToUuids(vector<unsigned char> p_ByteArray) = 0;
				


				void ByteArrayToUuid(vector<unsigned char>& p_array, int p_startingPoint, vector<uuid>& p_allUuids)
				{
					if (p_startingPoint + 16 < p_array.size())
					{
						uuid tempUuid;
						unsigned char uuidData[16];
						int currentPositon = p_startingPoint;
						for (int index = 0; index < sizeof(boost::uuids::uuid); index++, currentPositon++)
						{
							uuidData[index] = p_array[currentPositon];
						}

						memcpy(&tempUuid, uuidData, 16);

						p_allUuids.push_back(tempUuid);
					}
				}

				bool find(uuid p_inputtedId)
				{
					for (int index = 0; index < m_observableEvents.size(); index++)
					{
						if (m_observableEvents[index]->GetUuid() == p_inputtedId)
						{
							return true;
						}
					}
					return false;
				}

				

				string GetOEUuids()
				{
					stringstream ss;
					 
					for (int index = 0; index < m_observableEvents.size(); index++)
					{
						ss << m_observableEvents[index]->GetUuid();
						ss << ";";
					}
					return ss.str();
				}

				ObservableEvent<T,S>* GetObservableEvent(uuid p_oeUuid)
				{
					for (int index = 0; index < m_observableEvents.size(); index++)
					{
						if (m_observableEvents[index]->GetUuid() == p_oeUuid)
						{
							return m_observableEvents[index];
						}
					}

					return nullptr;
				}

				virtual void SerializeAllUuids(vector<unsigned char>&) = 0;
				virtual void GetNeededOE(vector<unsigned char>&, vector<unsigned char>&) = 0;

			protected:

				vector<ObservableEvent<T, S>*> m_observableEvents;

				FixedVectorTimer* m_peerLogicalTime;
				
				int m_lowEpochCounter;

				int m_highEpochCounter;

				vector<string> newUpdates;

				int m_amountPeers;

				///-------------------------------------------------------------------------------------------------
				/// <summary>	Pre function. checks that there are no concurrent events. </summary>
				///
				/// <remarks>	Adrian, 10/09/2015. </remarks>
				///
				/// <param name="p_lhs">	[in,out] If non-null, the left hand side. </param>
				/// <param name="p_rhs">	[in,out] If non-null, the right hand side. </param>
				///
				/// <returns>	true if it succeeds, false if it fails. </returns>
				///-------------------------------------------------------------------------------------------------

				bool preFunction(ObservableEvent<T,S>* p_lhs, ObservableEvent < T,S> * p_rhs)
				{
					if (p_lhs == p_rhs)
					{
						return false;
					}
					else if (p_lhs->GetTimeStamps() == p_rhs->GetTimeStamps())
					{
						return TieFunction(p_lhs, p_rhs);
					}
					else if (p_lhs->GetTimeStamps() <= p_rhs->GetTimeStamps())
					{
						return true;
					}
					else if (p_rhs->GetTimeStamps() <= p_lhs->GetTimeStamps())
					{
						return false;
					}
					else 
						return TieFunction(p_lhs, p_rhs);
				}

				bool TieFunction(ObservableEvent<T, S>* p_lhs, ObservableEvent < T,S> * p_rhs)
				{
					return (p_lhs->GetUuid() < p_rhs->GetUuid());
				}
		};
		//grouping function
	}
}
